/**
  *  @file       run_shell_cmd.cpp
  *  @author     陈猛
  *  @note       chen-meng@example.com
  *  @date       2019年09月25日 星期三 10时54分50秒
  *  @details    参考popen函数源码
**************************************************/

#include "events/common_log.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "run_shell_cmd.h"

RunShellCmd::RunShellCmd(const char *cmd)
    : RunCmdBase(cmd)
{
}

RunShellCmd::~RunShellCmd()
{
}

int RunShellCmd::system()
{
    volatile int failed = 0;
    pid_t pid;

    if (!argv[0])
    {
        GEN_Printf(LOG_ERROR, "Exe argue is null,");
        return -1;
    }

    pid = vfork();
    if (pid < 0)
    {
        GEN_Printf(LOG_ERROR, "vfork failed, %s", strerror(errno));
        return -1;
    }

    if (!pid)
    {
#if 0
        printf("name: %s, num: %d, alloc: %d\n", argv[0], nNum, nAlloc);
        for (int i = 0; i < nNum; ++i)
        {
            printf("%s \n", argv[i]);
        }
        printf("\n");
#endif        
        execvp(argv[0], (char *const*)argv);

        // execvp没有出错,就不会再执行下一步的打印信息
        printf("child[%s] exec over.\n", argv[0]);
        perror("execvp");
        failed = errno;
        _exit(111);
    }

    // GEN_Printf (LOG_DEBUG, "--%d, %d--", failed, pid);
    /* 父进程 */
    /* 根据标准，vfork()可以等效于fork()，我们不会看到“failed”的值。有兴趣的人可以等待pid并学习退出代码。如果111 -那么它(很可能)无法执行 */
    if (failed)
    {
        safe_waitpid(pid, NULL, 0); /* prevent zombie */
        errno = failed;
        return -1;
    }

    return wait4pid(pid);
}


RunPipeCmd::RunPipeCmd(const char *cmdName)
    : RunCmdBase(cmdName)
    , m_pid(-1), m_fd(-1)
{
}

RunPipeCmd::~RunPipeCmd()
{
    this->kill();
}

int RunPipeCmd::exec(int type)
{
    int pipefd[2] = {-1, -1};

    SF_ASSERT(type < RUNMAX);

    pipe(pipefd);

    m_pid = fork();
    if (m_pid < 0)
    {
        GEN_Printf(LOG_ERROR, "fork failed, %s", strerror(errno));
        return -1;
    }

    if (m_pid == 0) //child
    {
        if (READ == type)
        {
            close(pipefd[0]);
            if (pipefd[1] != STDOUT_FILENO)
            {
                dup2(pipefd[1], STDOUT_FILENO);
                close(pipefd[1]);
            }
        } else if (WRITE == type)
        {
            close(pipefd[1]);
            if (pipefd[0] != STDIN_FILENO)
            {
                dup2(pipefd[0], STDIN_FILENO);
                close(pipefd[0]);
            }
        }

        int ret = execvp(argv[0], (char *const*)argv);

        printf("RunPipeCmd: %s exit, ret = %d(%d, %s).\n", argv[0], ret, errno, strerror(errno));

        _exit(111);
    }

    if (READ == type)
    {
        close(pipefd[1]);
        m_fd = pipefd[0];
    } else if (WRITE == type)
    {
        close(pipefd[0]);
        m_fd = pipefd[1];
    }

    GEN_Printf(LOG_DEBUG, "pid=%d, fd=%d", m_pid, m_fd);
    return m_fd;
}

static inline bool isCheckRuning(pid_t pid)
{
    return (::getpgid(pid) == -1 && errno == ESRCH) ? false : true;
}

void RunPipeCmd::kill()
{
    if (m_pid > 0 && isCheckRuning(m_pid))
    {
        pid_t r;
        int status;

        ::kill(m_pid, SIGTERM);
        usleep(50000);
        r = waitpid(m_pid, &status, WNOHANG);

        while (!((0 < r) || ((r == -1) && (errno != EINTR))))
        {
            ::kill(m_pid, SIGKILL);
            usleep(50000);
            r = waitpid(m_pid, &status, WNOHANG);
            GEN_Printf(LOG_DEBUG, "waitpid ret: %d, (%d, %s)", r, errno, strerror(errno));
        }
        m_pid = -1;
    }

    if (m_fd > 0)
    {
        ::close(m_fd);
        m_fd = -1;
    }
}

int RunPipeCmd::readData(char *buff, int len, int msec)
{
    int ret = -1;
    fd_set rfds;
    struct timeval tv = { msec / 1000, msec % 1000 };

    FD_ZERO(&rfds);
    FD_SET(m_fd, &rfds);

    ret = select(m_fd + 1, &rfds, NULL, NULL, &tv);

    if (0 < ret)
    {
        if (FD_ISSET(m_fd, &rfds))
        {
            ret = ::read(m_fd, buff, len);
        } else
        {
            ret = -1;
        }
    } else if (0 == ret)      //超时
    {
        ret = -2;
        GEN_Printf(LOG_ERROR, "read select timeout!");
    } else
    {
        GEN_Printf(LOG_ERROR, "read select failed, %s", strerror(errno));
    }

    return ret;
}

int RunPipeCmd::writeData(const char *buff, int len, int msec)
{
    int ret = -1;
    fd_set wfds;
    struct timeval tv = { msec / 1000, msec % 1000 };

    FD_ZERO(&wfds);
    FD_SET(m_fd, &wfds);

    ret = select(m_fd + 1, NULL, &wfds, NULL, &tv);

    if (0 < ret)
    {
        ret = ::write(m_fd, buff, len);
    } else if (0 == ret)      //超时
    {
        ret = 0;
        GEN_Printf(LOG_ERROR, "write select timeout!");
    } else
    {
        ret = -1;
        GEN_Printf(LOG_ERROR, "write select failed, %s", strerror(errno));
    }

    return ret;
}

int RunPipeCmd::readn(char *buff, int len)
{
    return ::read(m_fd, buff, len);
}

int RunPipeCmd::writen(char *buff, int len)
{
    return ::write(m_fd, buff, len);
}
